﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;

namespace Test
{
    class ClientEventArgs : EventArgs
    {
        public Socket Client { get; private set; }
        public ClientEventArgs(Socket client)
        {
            Client = client;
        }
    }
    class EventArgs<T> : EventArgs
    {
        public T Args { get; private set; }
        public EventArgs(T args)
        {
            Args = args;
        }
    }
    class TcpSvr
    {
        public event EventHandler<ClientEventArgs> ClientConnected;
        protected virtual void OnClientConnected(ClientEventArgs e)
        {
            if (ClientConnected != null) ClientConnected(this, e);
        }

        public event EventHandler<EventArgs<Exception>> Error;
        protected virtual void OnError(EventArgs<Exception> e)
        {
            if (Error != null) Error(this, e);
        }
        private readonly int _port;
        private Socket _socket;
        private readonly List<object> _sync;
        protected bool GetSync(string token) { lock (_sync) { return _sync.Contains(token); } }
        protected void SetSync(string token, bool enable)
        {
            lock (_sync)
            {
                var contain = _sync.Contains(token);
                if ((enable && contain) || (!enable && !contain)) return;
                if (contain)
                    _sync.Remove(token);
                else
                    _sync.Add(token);
            }

        }
        public bool IsRunning
        {
            get { return GetSync("IsRunning"); }
            private set { SetSync("IsRunning", value); }
        }
        protected bool KeepRunning
        {
            get { return GetSync("KeepRunning"); }
            set { SetSync("KeepRunning", value); }
        }
        public TcpSvr(int port)
        {
            _sync = new List<object>();
            _port = port;
        }
        public void Start()
        {
            if (IsRunning) return;
            KeepRunning = true;
            IsRunning = true;
            try
            {
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _socket.Bind(new IPEndPoint(IPAddress.Any, _port));
                _socket.Listen(8);
                new Thread(AcceptClient) { IsBackground = true }.Start();
            }
            catch (Exception)
            {
                KeepRunning = false;
                IsRunning = false;
                throw;
            }
        }
        protected virtual void AcceptClient()
        {
            try
            {
                while (KeepRunning)
                {
                    var client = _socket.Accept();
                    OnClientConnected(new ClientEventArgs(client));
                }
            }
            catch (SocketException ex)
            {
                if (KeepRunning)
                    OnError(new EventArgs<Exception>(ex));
            }
            catch (Exception ex)
            {
                OnError(new EventArgs<Exception>(ex));
            }
            KeepRunning = false;
            IsRunning = false;
            _socket.Close();
            _socket = null;
        }

        public void Stop()
        {
            if (!IsRunning) return;
            if (_socket == null) return;
            KeepRunning = false;
            _socket.Close(100);
        }
    }
    class SocketService : IDisposable
    {
        private Socket _client;
        private int _sent;
        public SocketService(Socket client)
        {
            _client = client;
        }
        public bool Send(byte[] buffer)
        {
            _client.Send(buffer);
            _sent++;
            return _sent < 10;
        }
        public void Dispose()
        {
            _client.Close(500);
            _client = null;
        }
    }
    class Program
    {

        static readonly List<SocketService> Clients = new List<SocketService>();
        static readonly List<object> _sync = new List<object>();
        static bool GetSync(string token) { lock (_sync) { return _sync.Contains(token); } }
        static void SetSync(string token, bool enable)
        {
            lock (_sync)
            {
                var contain = _sync.Contains(token);
                if ((enable && contain) || (!enable && !contain)) return;
                if (contain)
                    _sync.Remove(token);
                else
                    _sync.Add(token);
            }
        }
        static void ReportTime()
        {

            //for /L %t in (1,1,16) do start TELNET 127.0.0.1 9999
            while (GetSync("KeepRunning"))
            {
                var time = DateTime.Now;
                var d = Encoding.ASCII.GetBytes(time.ToString("yyyy/MM/dd HH:mm:ss.ffffff\r\n"));
                var list = new List<SocketService>();
                foreach (var c in Clients.ToArray())
                {
                    try
                    {
                        if (!c.Send(d)) list.Add(c);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                foreach (var c in list)
                {
                    Clients.Remove(c);
                    c.Dispose();
                }
                var sleep = time.AddSeconds(1).Subtract(DateTime.Now).TotalMilliseconds;
                if (sleep > 0) Thread.Sleep((int)sleep);
            }
        }
        static void Main()
        {
            SetSync("KeepRunning", true);
            new Thread(ReportTime) { IsBackground = true }.Start();
            var server = new TcpSvr(9999);
            var counter = 0;
            server.ClientConnected +=
                (s, e) =>
                    {
                        Clients.Add(new SocketService(e.Client));
                        counter++;
                        Console.WriteLine("{0:X4} {1}", counter, e.Client.RemoteEndPoint);
                    };
            server.Start();
            while (GetSync("KeepRunning"))
            {
                var command = Console.ReadLine();
                if (string.IsNullOrEmpty(command)) continue;
                switch (command.ToLower())
                {
                    case "exit":
                        SetSync("KeepRunning", false);
                        break;
                    case "cls":
                        Console.Clear();
                        break;
                }
            }
            server.Stop();


            return;
            var examinees =
                new List<Examinee>
                    {
                        new Examinee {Organization="Org0", Title="Title0", Name = "Examinee0", TagId = "000000"},
                        new Examinee {Organization="Org1", Title="Title1", Name = "Examinee1", TagId = "000001"},
                        new Examinee {Organization="Org2", Title="Title2", Name = "Examinee2", TagId = "000002"},
                        new Examinee {Organization="Org3", Title="Title3", Name = "Examinee3", TagId = "000003"},
                        new Examinee {Organization="Org4", Title="Title4", Name = "Examinee4", TagId = "000004"},
                        new Examinee {Organization="Org5", Title="Title5", Name = "Examinee5", TagId = "000005"},
                        new Examinee {Organization="Org6", Title="Title6", Name = "Examinee6", TagId = "000006"},
                        new Examinee {Organization="Org7", Title="Title7", Name = "Examinee7", TagId = "000007"},
                        new Examinee {Organization="Org8", Title="Title8", Name = "Examinee8", TagId = "000008"},
                        new Examinee {Organization="Org9", Title="Title9", Name = "Examinee9", TagId = "000009"},
                    };
            var readers =
                new List<ReaderConfig>
                    {
                        BuildReaderConfig("000000", "000000", "192.168.1.10", 4000),
                        BuildReaderConfig("000001", "000001", "192.168.1.11", 4000),
                        BuildReaderConfig("000002", "000002", "192.168.1.12", 4000),
                        BuildReaderConfig("000003", "000003", "192.168.1.13", 4000),
                        BuildReaderConfig("000004", "000004", "192.168.1.14", 4000),
                        BuildReaderConfig("000005", "000005", "192.168.1.15", 4000),
                        BuildReaderConfig("000006", "000006", "192.168.1.16", 4000),
                        BuildReaderConfig("000007", "000007", "192.168.1.17", 4000),
                        BuildReaderConfig("000008", "000008", "192.168.1.18", 4000),
                        BuildReaderConfig("000009", "000009", "192.168.1.19", 4000),
                        BuildReaderConfig("000010", "000010", "192.168.1.20", 4000),
                        BuildReaderConfig("000011", "000011", "192.168.1.21", 4000),
                        BuildReaderConfig("000012", "000012", "192.168.1.22", 4000),
                        BuildReaderConfig("000013", "000013", "192.168.1.23", 4000),
                        BuildReaderConfig("000014", "000014", "192.168.1.24", 4000),
                        BuildReaderConfig("000015", "000015", "192.168.1.25", 4000),
                        BuildReaderConfig("000016", "000016", "192.168.1.26", 4000),
                        BuildReaderConfig("000017", "000017", "192.168.1.27", 4000),
                        BuildReaderConfig("000018", "000018", "192.168.1.28", 4000),
                        BuildReaderConfig("000019", "000019", "192.168.1.29", 4000),
                    };
            var map =
                new Map
                {
                    Name = "Test",
                    Background = new Bitmap(640, 480, PixelFormat.Format24bppRgb),
                };
            map.Areas.AddRange(
                new[]
                    {
                        BuildArea("Area00", Color.Black, Color.White, readers[0], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area01", Color.Black, Color.White, readers[1], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area02", Color.Black, Color.White, readers[2], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area03", Color.Black, Color.White, readers[3], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area04", Color.Black, Color.White, readers[4], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area05", Color.Black, Color.White, readers[5], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area06", Color.Black, Color.White, readers[6], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area07", Color.Black, Color.White, readers[7], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area08", Color.Black, Color.White, readers[8], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                        BuildArea("Area09", Color.Black, Color.White, readers[9], P(0, 0), P(5, 0), P(5, 5), P(0, 5)),
                    });
            var route = new RouteTable();
            route.Add("000000", "000001");
            route.Add("000001", "000002", "000003", "000004");
            route.Add("000002", "000003", "000004");
            route.Add("000003", "000002", "000004");
            route.Add("000004", "000005", "000006");
            route.Add("000005", "000006", "000007");
            route.Add("000006", "000005", "000007");
            route.Add("000007", "000008");
            route.Add("000008", "000009");

            var exam =
                new ExamLog
                {
                    StartTime = DateTime.Now,
                    RouteTable = route,
                    Map = map,
                };
            exam.Examinees.AddRange(examinees);
            var o = TestA(exam);

            Console.ReadLine();
        }

        private delegate void Action<T1, T2, T3, T4>(T1 a1, T2 a2, T3 a3, T4 a4);
        private static void RouteTest()
        {
            var list = new List<string>();
            Action<RouteTable, string, string, string> check =
                (table, from, path, result) =>
                {
                    list.Clear();
                    for (var i = 0; i < path.Length; i++)
                        list.Add(table.IsValid(from, path[i].ToString()) ? "1" : "0");
                    var test = string.Join("", list.ToArray());
                    Console.WriteLine("{0} => {1} : {2} {3} {4}", from, path, result, test, test == result ? "" : "ERROR");
                };
            const string a = "A";
            const string b = "B";
            const string c = "C";
            const string d = "D";
            const string e = "E";
            const string f = "F";

            var tb = new RouteTable();
            tb.Add(a, b, c, d);
            tb.Add(b, c, d, e);
            tb.Add(c, d, e);
            tb.Add(d, e);
            tb.Add(f, b);

            check(tb, "A", "ABCDEF", "011100");
            check(tb, "B", "ABCDEF", "001110");
            check(tb, "C", "ABCDEF", "000110");
            check(tb, "D", "ABCDEF", "000010");
            check(tb, "E", "ABCDEF", "000000");
            check(tb, "F", "ABCDEF", "010000");
            check(tb, "G", "ABCDEF", "000000");

            var startPoints = tb.FindStartPoints();
            Console.WriteLine("START: {0}", string.Join(", ", startPoints));
            var endPoints = tb.FindEndPoints();
            Console.WriteLine("END: {0}", string.Join(", ", endPoints));

            var tb2 = TestA(tb);
            Console.WriteLine(tb2.Table.Count);
            Console.ReadLine();
        }

        static T TestA<T>(T obj)
        {
            var buffer = Serialize(obj);
            return Deserialize<T>(buffer);
        }
        static bool TestSerializable(object obj)
        {
            var buffer = Serialize(obj);
            var o = Deserialize<object>(buffer);
            return o.Equals(obj);
        }
        static ReaderConfig BuildReaderConfig(string id, string name, string ip, int port)
        {
            return new ReaderConfig { Id = id, Name = name, IpAddress = ip, Port = port };
        }
        static Area BuildArea(string name, Color foreColor, Color backColor, ReaderConfig reader, params Point[] points)
        {
            var area = new Area { Name = name, ForeColor = foreColor, BackColor = backColor, Reader = reader };
            area.Points.AddRange(points);
            return area;
        }
        static Point P(int x, int y) { return new Point(x, y); }
        static byte[] Serialize(object obj)
        {
            var bf = new BinaryFormatter();
            using (var mem = new MemoryStream())
            {
                bf.Serialize(mem, obj);
                return mem.ToArray();
            }
        }
        static T Deserialize<T>(byte[] buffer)
        {
            using (var mem = new MemoryStream(buffer))
            {
                return Deserialize<T>(mem);
            }
        }
        static T Deserialize<T>(Stream stream)
        {
            var bf = new BinaryFormatter();
            return (T)bf.Deserialize(stream);
        }
    }
 
    [Serializable]
    public class ReaderConfig
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string IpAddress { get; set; }
        public int Port { get; set; }
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return obj.GetType() == typeof(ReaderConfig) && Equals((ReaderConfig)obj);
        }
        public bool Equals(ReaderConfig other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.Id, Id) && Equals(other.Name, Name) && Equals(other.IpAddress, IpAddress) && other.Port == Port;
        }
        public override int GetHashCode()
        {
            unchecked
            {
                var result = (Id != null ? Id.GetHashCode() : 0);
                result = (result * 397) ^ (Name != null ? Name.GetHashCode() : 0);
                result = (result * 397) ^ (IpAddress != null ? IpAddress.GetHashCode() : 0);
                result = (result * 397) ^ Port;
                return result;
            }
        }
        public static bool operator ==(ReaderConfig a1, ReaderConfig a2)
        {
            if (ReferenceEquals(a1, null) && ReferenceEquals(a2, null)) return true;
            if (ReferenceEquals(a1, null) || ReferenceEquals(a2, null)) return false;
            return a1.GetHashCode() == a2.GetHashCode();
        }
        public static bool operator !=(ReaderConfig a1, ReaderConfig a2)
        {
            return !(a1 == a2);
        }
        public override string ToString()
        {
            return string.Format("{{{0}:{1} {2}:{3}}}", Id, Name, IpAddress, Port);
        }
    }
    [Serializable]
    public class Area
    {
        public string Name { get; set; }
        public ReaderConfig Reader { get; set; }
        public Color BackColor { get; set; }
        public Color ForeColor { get; set; }
        public List<Point> Points { get; private set; }
        public Area()
        {
            Points = new List<Point>();
        }
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return obj.GetType() == typeof(Area) && Equals((Area)obj);
        }
        public bool Equals(Area other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.Name, Name) && Equals(other.Reader, Reader) && other.BackColor.Equals(BackColor) && other.ForeColor.Equals(ForeColor);
        }
        public override int GetHashCode()
        {
            unchecked
            {
                var result = (Name != null ? Name.GetHashCode() : 0);
                result = (result * 397) ^ (Reader != null ? Reader.GetHashCode() : 0);
                result = (result * 397) ^ BackColor.GetHashCode();
                result = (result * 397) ^ ForeColor.GetHashCode();
                return result;
            }
        }
        public static bool operator ==(Area a1, Area a2)
        {
            if (ReferenceEquals(a1, null) && ReferenceEquals(a2, null)) return true;
            if (ReferenceEquals(a1, null) || ReferenceEquals(a2, null)) return false;
            return a1.GetHashCode() == a2.GetHashCode();
        }
        public static bool operator !=(Area a1, Area a2)
        {
            return !(a1 == a2);
        }
        public override string ToString()
        {
            return string.Format("{0}:{1}", Name, Reader);
        }
    }
    [Serializable]
    public class Map
    {
        public string Name { get; set; }
        public Bitmap Background { get; set; }
        public List<Area> Areas { get; private set; }

        public Map()
        {
            Areas = new List<Area>();
        }
        public bool TryGetArea(string readerId, out Area area)
        {
            foreach (var i in Areas)
            {
                if (i.Reader == null || i.Reader.Id != readerId) continue;
                area = i;
                return true;
            }
            area = null;
            return false;
        }
    }

    [Serializable]
    public class RouteTable
    {
        public Dictionary<string, List<string>> Table { get; private set; }
        public RouteTable()
        {
            Table = new Dictionary<string, List<string>>();
        }
        public void Add(string from, params string[] to)
        {
            if (from == null) throw new ArgumentNullException("from");
            if (to == null) throw new ArgumentNullException("to");
            if (from.Equals(to)) throw new ArgumentException("from and to are equal.");
            foreach (var i in to)
            {
                if (!Table.ContainsKey(from))
                    Table[from] = new List<string> { i };
                else
                    Table[from].Add(i);
            }
        }
        public void Remove(string from, string to)
        {
            if (from == null || to == null) return;
            if (!Table.ContainsKey(from)) return;
            if (!Table[from].Contains(to)) return;
            Table[from].Remove(to);
        }
        public bool IsValid(string from, string to)
        {
            return Table.ContainsKey(from) && Table[from].Contains(to);
        }

        private Dictionary<string, int[]> GetUsages()
        {
            var list = new Dictionary<string, int[]>();
            foreach (var i in Table)
            {
                if (!list.ContainsKey(i.Key))
                    list[i.Key] = new int[2];
                list[i.Key][0]++;
                foreach (var v in i.Value)
                {
                    if (!list.ContainsKey(v))
                        list[v] = new int[2];
                    list[v][1]++;
                }
            }
            return list;
        }
        public string[] FindStartPoints()
        {
            var usages = GetUsages();
            var list = new List<string>();
            foreach (var i in usages)
                if (i.Value[1] < 1) list.Add(i.Key);
            return list.ToArray();
        }
        public string[] FindEndPoints()
        {
            var usages = GetUsages();
            var list = new List<string>();
            foreach (var i in usages)
                if (i.Value[0] < 1) list.Add(i.Key);
            return list.ToArray();
        }
    }

    [Serializable]
    public class Examinee
    {
        public string Organization { get; set; }
        public string Title { get; set; }
        public string Name { get; set; }
        public string TagId { get; set; }

        public override string ToString()
        {
            return string.Format("{0}: {1}", TagId, Name);
        }
    }
    public interface ITagData
    {
        string ReaderId { get; }
        string TagId { get; }
        int SerialNumber { get; }
        int RSSI { get; }
        bool TakingOff { get; }
        TimeSpan IdleTime { get; }
        bool KeyPressed { get; }
    }
    [Serializable]
    public class TagData : ITagData
    {
        private readonly string _raw;
        private readonly int _status;
        public string ReaderId { get; set; }
        public string Group { get; set; }
        public string TagId { get; set; }
        public int RSSI { get; set; }
        public float? Temperature { get; set; }
        public int SerialNumber { get; set; }

        public bool KeyPressed { get { return (_status & 0x01) == 0x01; } }
        public bool BatteryLow { get { return (_status & 0x02) == 0x02; } }
        public bool TakingOff { get { return (_status & 0x04) == 0x04; } }
        public bool Idle { get { return (_status & 0x08) == 0x08; } }
        public string RawData { get { return _raw; } }
        public DateTime Time { get; private set; }
        public TimeSpan IdleTime { get; set; }


        public TagData(string raw)
            : this(DateTime.Now, raw)
        {
        }
        public TagData(DateTime time, string raw)
        {
            //ReaderID(6),Group(2),TagID(6),Status(1),RSSI(2),Temperature(3)
            //ReaderID(6),Group(2),TagID(6),Status(1),RSSI(2),SerialNumber(5),Temperature(3)
            if (raw.Length != 17 && raw.Length != 20 && raw.Length != 22 && raw.Length != 25)
                throw new ArgumentException("invalid raw data");
            Time = time;
            _raw = raw;
            ReaderId = raw.Substring(0, 6);
            Group = raw.Substring(6, 2);
            TagId = raw.Substring(8, 6);
            _status = int.Parse(raw.Substring(14, 1), NumberStyles.HexNumber);
            RSSI = int.Parse(raw.Substring(15, 2));
            int sn;
            switch (raw.Length)
            {
                case 20:
                    Temperature = float.Parse(raw.Substring(17, 3)) / 10;
                    break;
                case 22:
                    if (int.TryParse(raw.Substring(17, 5), out sn))
                        SerialNumber = sn;
                    break;
                case 25:
                    if (int.TryParse(raw.Substring(17, 5), out sn))
                        SerialNumber = sn;
                    Temperature = float.Parse(raw.Substring(22, 3)) / 10;
                    break;
            }
            //IdleTime = time.Subtract(lastReceived);
        }

        public override string ToString()
        {
            var status =
                string.Format("[{0:X}]", _status) +
                (KeyPressed ? "呼" : "__") +
                (BatteryLow ? "電" : "__") +
                (TakingOff ? "脫" : "__") +
                (Idle ? "停" : "__");
            return string.Format(
                "TAG:{2}:{5:00000}, A:{0}:RSSI:{3:00}, G:{1}, {4}",
                ReaderId, Group, TagId, RSSI, status, SerialNumber);
        }
    }
    [Serializable]
    public class AreaLog
    {
        public Area Area { get; set; }
        public List<TagData> TagDatas { get; private set; }
        public AreaLog()
        {
            TagDatas = new List<TagData>();
        }
    }
    [Flags]
    public enum TagStatus
    {
        Accepted = 0x00,
        Expired = 0x01,
        InvalidRoute = 0x02,
    }

    [Serializable]
    public class ExamineeLog
    {
        private int? _serialNumber;
        private AreaLog _currentArea;
        public Examinee Examinee { get; set; }
        public List<AreaLog> AreaLogs { get; private set; }
        public ExamineeLog()
        {
            AreaLogs = new List<AreaLog>();
        }
        private bool IsFirstEntry() { return _currentArea == null; }
        private bool IsUnknowArea() { return _currentArea.Area == null; }
        private bool IsDifferentArea(string readerId) { return _currentArea.Area.Reader.Id != readerId; }

        public TagStatus Add(ExamLog examLog, TagData tag)
        {
            if (IsExpired(tag)) return TagStatus.Expired;
            if (IsInvalidRoute(examLog.RouteTable, tag)) return TagStatus.InvalidRoute;
            if (IsFirstEntry() || IsUnknowArea() || IsDifferentArea(tag.ReaderId))
            {
                _currentArea = new AreaLog();
                Area area;
                if (!examLog.Map.TryGetArea(tag.ReaderId, out area))
                    area = CreateUnknowArea(tag);
                _currentArea.Area = area;
                AreaLogs.Add(_currentArea);
            }
            _currentArea.TagDatas.Add(tag);
            _serialNumber = tag.SerialNumber;
            return TagStatus.Accepted;
        }
        private static Area CreateUnknowArea(TagData tag)
        {
            return new Area { Name = tag.ReaderId };
        }
        private bool IsExpired(TagData tag)
        {
            return
                _serialNumber.HasValue
                ? (tag.SerialNumber < _serialNumber.Value)
                : false;
        }
        private bool IsInvalidRoute(RouteTable routeTable, TagData tag)
        {
            if (routeTable == null) return false;
            if (_currentArea == null) return Array.IndexOf(routeTable.FindStartPoints(), tag.TagId) > -1;
            return routeTable.IsValid(_currentArea.Area.Reader.Id, tag.ReaderId);
        }
    }
    [Serializable]
    public class ExamLog
    {
        public DateTime? StartTime { get; set; }
        public DateTime? EndTime { get; set; }
        public Map Map { get; set; }
        public RouteTable RouteTable { get; set; }
        public List<Examinee> Examinees { get; private set; }
        public Dictionary<string, ExamineeLog> ExamineeLogs { get; private set; }
        public ExamLog()
        {
            Examinees = new List<Examinee>();
            ExamineeLogs = new Dictionary<string, ExamineeLog>();
        }
        private static Examinee CreateUnknowExaminee(TagData tag)
        {
            return
                new Examinee
                {
                    Name = tag.TagId,
                    Organization = string.Empty,
                    TagId = tag.TagId,
                    Title = string.Empty
                };
        }
        private bool TryGetExaminee(TagData tag, out Examinee examinee)
        {
            for (var i = 0; i < Examinees.Count; i++)
            {
                if (Examinees[i].TagId != tag.TagId) continue;
                examinee = Examinees[i];
                return true;
            }
            examinee = null;
            return false;
        }
        public TagStatus Add(TagData tag)
        {
            if (!ExamineeLogs.ContainsKey(tag.TagId))
            {
                Examinee examinee;
                if (!TryGetExaminee(tag, out examinee))
                    examinee = CreateUnknowExaminee(tag);
                ExamineeLogs.Add(tag.TagId, new ExamineeLog { Examinee = examinee });
            }
            return ExamineeLogs[tag.TagId].Add(this, tag);
        }
    }
}